Z, W, P functions

Analogs:

Z: zernike
W: wavefront
P: transform

These methods avoid plotting and instead return (ρ, θ) functions as essentially closures, but packaged within Polynomial and WavefrontError types. The pupil can then be evaluated using these functions with polar coordinates:

Z40 = Z(0, 4)
Z40(0.7, π/4)

For wavefront reconstruction this is equivalent to ΔW(ρ, θ) = ∑aᵢZᵢ(ρ, θ) where aᵢ and Zᵢ were determined from the fitting process according to precision.

Arithmetric between these types is defined using the usual operators such that wavefront error approximations essentially form a commutative ring (with associativity of multiplication being approximate) expressed in a Zernike basis.

In addition, the Zernike.Superposition(W) and Zernike.Product(W) constructors (where W is a Vector{WavefrontError}) serve as direct methods for creating composite functions which group evaluate a specified expansion set when an updated set of coefficients is not required.

Derivatives

Zernike.derivatives(Z::Polynomial, order::Int = 1) computes the nth order partial derivatives of Z(ρ, θ) and returns the two-tuple (∂Z/∂ρ, ∂Z/∂θ) containing the PartialDerivative types.

Zernike.Gradient(Z::Polynomial) wraps the first-order partial derivatives and returns a callable ∇Z(ρ, θ).

The partials and gradient are also functors which can be evaluated over the pupil. In addition, partial derivatives can easily be plotted by simply calling them with no arguments and their Unicode representation extracted by calling them with the String type.

julia> ∂Z_∂ρ, ∂Z_∂θ = Zernike.derivatives(Z(4, 4));

julia> ∂Z_∂ρ() # plots it over the pupil

julia> ∂Z_∂ρ(String)
"√(10)4ρ³cos(4θ)"